﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using Petzold.Media3D;
using GeometryViz3D.Model;

namespace GeometryViz3D.Views
{
    /// <summary>
    /// Interaction logic for viewport.xaml
    /// </summary>
    public partial class G3DViewport : UserControl
    {
        const double AXIS_TEXT_SIZE = 0.2;

        DirectionalLight m_directionalLight = new DirectionalLight();
        AmbientLight m_ambientLight = new AmbientLight();

        PerspectiveCamera m_camera = new PerspectiveCamera();

        Axes m_axes;

        AxisAngleRotation3D m_axisX = new AxisAngleRotation3D();
        AxisAngleRotation3D m_axisY = new AxisAngleRotation3D();
        AxisAngleRotation3D m_axisZ = new AxisAngleRotation3D();

        G3DModel m_model;
        Visual3D m_visual3D;

        public G3DViewport()
        {
            InitializeComponent();
        }

        public double Extent
        {
            get { return m_axes.Extent; }
        }

        #region Dependency Property

        public static readonly DependencyProperty DistanceProperty =
            DependencyProperty.Register("Distance",
            typeof(int),
            typeof(G3DViewport),
            new PropertyMetadata(0, DistancePropertyChanged));

        public int Distance
        {
            set { SetValue(DistanceProperty, value); }
            get { return (int)GetValue(DistanceProperty); }
        }

        protected static void DistancePropertyChanged
            (
            DependencyObject obj,
            DependencyPropertyChangedEventArgs args
            )
        {
            if (obj != null)
            {
                (obj as G3DViewport).DistancePropertyChanged(args);
            }
        }

        protected void DistancePropertyChanged(DependencyPropertyChangedEventArgs args)
        {
            var p = m_camera.Position;

            var currentDistance = Math.Sqrt(p.X * p.X + p.Y * p.Y + p.Z * p.Z);
            var ratio = Math.Sqrt(Distance / currentDistance);

            m_camera.Position = new Point3D(p.X * ratio, p.Y * ratio, p.Z * ratio);
        }

        #endregion

        public G3DModel Model
        {
            get
            {
                return m_model;
            }

            set
            {
                if (m_visual3D != null)
                {
                    viewport.Children.Remove(m_visual3D);
                }

                m_model = value;

                int ratio = CalculateRatio();

                // Change the Extent of the Axes and the Position of the Camera
                m_axes.UnitsPerBigTick = ratio;
                var extent = m_model.Extent * 2;

                if (extent < 10)
                {
                    extent = 10;
                }

                m_axes.Extent = extent;

                m_visual3D = m_model.CreateVisual3D(ratio);
 
                m_camera.Position = new Point3D(0, 0, extent * 2);

                viewport.Children.Add(m_visual3D);
            }
        }

        private int CalculateRatio()
        {
            // in inches
            double viewportSize = Math.Min(viewport.ActualWidth, viewport.ActualHeight) / 96;
            int ratio = (int)(m_model.Extent / viewportSize + 0.5);

            if (ratio > 10)
            {
                ratio = ratio / 10 * 10;
                if (ratio % 10 >= 5)
                {
                     ratio += 10;
                }
            }
            else if (ratio > 5)
            {
                ratio = 10;
            }
            else if (ratio > 2)
            {
                ratio = 5;
            }
            else
            {
                ratio = 1;
            }

            return ratio;
        }

        public AxisAngleRotation3D AxisX
        {
            get { return m_axisX; }
        }

        public AxisAngleRotation3D AxisY
        {
            get { return m_axisY; }
        }

        public AxisAngleRotation3D AxisZ
        {
            get { return m_axisZ; }
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            {
                // Axes
                m_axes = new Axes();
                m_axes.Extent = 10;
                m_axes.FontSize = AXIS_TEXT_SIZE;
                viewport.Children.Add(m_axes);
            }
            {
                // Lights
                m_directionalLight.Color = Color.FromRgb(0x80, 0x80, 0x80);
                m_directionalLight.Direction = new Vector3D(2, -3, -1);

                m_ambientLight.Color = Color.FromRgb(0x80, 0x80, 0x80);

                var visual = new ModelVisual3D();

                var group = new Model3DGroup();
                group.Children.Add(m_directionalLight);
                group.Children.Add(m_ambientLight);

                visual.Content = group;

                viewport.Children.Add(visual);
            }
            {
                // Camera
                m_camera.Position = new Point3D(0, 0, 20);
                m_camera.UpDirection = new Vector3D(0, 1, 0);
                m_camera.LookDirection = new Vector3D(0, 0, -1);
                m_camera.FieldOfView = 45;

                m_axisX.Axis = new Vector3D(1, 0, 0);
                m_axisY.Axis = new Vector3D(0, 1, 0);
                m_axisZ.Axis = new Vector3D(0, 0, 1);

                m_axisX.Angle = -15;
                m_axisY.Angle = 15;
                m_axisZ.Angle = 0;

                var group = new Transform3DGroup();

                var x = new RotateTransform3D(m_axisX);
                var y = new RotateTransform3D(m_axisY);
                var z = new RotateTransform3D(m_axisZ);

                group.Children.Add(x);
                group.Children.Add(y);
                group.Children.Add(z);

                m_camera.Transform = group;

                viewport.Camera = m_camera;
            }
        }
    }
}
